﻿using ClangSharp.Interop;
using CppAst;
using Microsoft.VisualBasic;

namespace ast;

public class ModuleInfo
{
    public CppCompilation CppCompilation;
    public Dictionary<CppClass, ClassInfo> CppTypeToClassInfo = new();
    public SortedDictionary<string, FileInfo> FileInfos = new();
    public SortedDictionary<string, List<FunctionInfo>> GlobalFunction = new();
    public SortedDictionary<string, EnumInfo> GlobalEnum = new();
    public string ModuleDir;
    public string ModuleName;
    public SortedDictionary<string, ClassInfo> Unsolved = new();
    public SortedDictionary<string, List<string>> extraFileInclude;
    public bool platformOnly = false;
    public SortedDictionary<string, CppClass> cppClassMap = new();
    public Dictionary<CXCursor, CppFunction> cursorToFunction;

    public ModuleInfo(string moduleName, string moduleDir, CppCompilation compilation,
        SortedDictionary<string, List<string>> extraFileInclude,
        bool usePlatformFile)
    {
        this.platformOnly = usePlatformFile;
        this.extraFileInclude = extraFileInclude;
        ModuleName = moduleName;
        ModuleDir = moduleDir;
        CppCompilation = compilation;
        collectClasses(compilation);
        initCppContainer(CppCompilation);
        postProcess();
    }

    private void postProcess()
    {
        foreach (var (moduleFile, extraInclude) in this.extraFileInclude)
        {
            var path = Path.Join(ModuleDir, moduleFile);
            this.FileInfos.TryGetValue(path, out var fileInfo);
            if (fileInfo != null)
            {
                foreach (var se in extraInclude)
                {
                    var include = Path.Join(ModuleDir, se);
                    fileInfo.SortedImport.Add(include);
                }
            }
        }

        this.cursorToFunction = this.CppCompilation.cursorToFuntion;

    }

    private void collectClasses(ICppGlobalDeclarationContainer declaration)
    {
        collectClassForContainer(declaration);

        foreach (var declarationNamespace in declaration.Namespaces)
        {
            collectClasses(declarationNamespace);
        }
    }

    private void collectClassForContainer(ICppGlobalDeclarationContainer declaration)
    {
        foreach (CppClass declarationClass in declaration.Classes)
        {
            collectClassForClass(declarationClass);
        }
    }

    private void collectClassForClass(CppClass cppClass)
    {
        cppClassMap.TryAdd(cppClass.FullName, cppClass);
        foreach (var subClass in cppClass.Classes)
        {
            collectClassForClass(subClass);
        }
    }

    private void initCppContainer(ICppGlobalDeclarationContainer declaration)
    {
        initClasses(declaration);
        initGlobalFunctions(declaration);
        initGlobalEnums(declaration);
        foreach (var declarationNamespace in declaration.Namespaces)
        {
            initCppContainer(declarationNamespace);
        }
    }

    private void initGlobalEnums(ICppGlobalDeclarationContainer declarationContainer)
    {
        AddEnums(declarationContainer.Enums);
    }

    private void AddEnums(CppContainerList<CppEnum> enumDefs)
    {
        foreach (var cppEnum in enumDefs)
        {
            if (string.IsNullOrEmpty(cppEnum.FullName))
            {
                continue;
            }

            if (!cppEnum.SourceFile.StartsWith(this.ModuleDir))
            {
                continue;
            }

            FileInfos.TryGetValue(cppEnum.SourceFile, out var fileInfo);
            if (fileInfo == null)
            {
                fileInfo = new FileInfo(cppEnum.SourceFile, this);
                FileInfos.Add(cppEnum.SourceFile, fileInfo);
            }

            var enumInfo = new EnumInfo(cppEnum, fileInfo);
            for (;;)
            {
                var addOk = GlobalEnum.TryAdd(cppEnum.FullName, enumInfo);
                if (addOk)
                {
                    break;
                }

                cppEnum.Name += "_";
            }
        }
    }


    private void initGlobalFunctions(ICppGlobalDeclarationContainer cppCompilationFunctions)
    {
        foreach (var function in cppCompilationFunctions.Functions)
        {
            if (ClassUtil.FunctionCanNotExport(this, function, null))
            {
                continue;
            }

            if (!function.SourceFile.StartsWith(this.ModuleDir))
            {
                continue;
            }

            FileInfos.TryGetValue(function.SourceFile, out var fileInfo);
            if (fileInfo == null)
            {
                fileInfo = new FileInfo(function.SourceFile, this);
                FileInfos.Add(function.SourceFile, fileInfo);
            }

            AddFunctionToSortedList(function, null, fileInfo, GlobalFunction);
        }
    }


    private void initClasses(ICppGlobalDeclarationContainer cppCompilation)
    {
        var classToPath = new Dictionary<CppClass, string>();
        foreach (var cppClass in cppCompilation.Classes)
        {
            if (cppClass.SourceFile == null || !cppClass.SourceFile.StartsWith(this.ModuleDir))
            {
                continue;
            }

            AddOneClass(cppClass, classToPath);
        }

        foreach (var (cppClass, sourceFilePath) in classToPath)
        {
            var classInfo = new ClassInfo();
            classInfo.ClassType = cppClass;
            classInfo.ModuleInfo = this;
            classInfo.FileInfo = null;

            
            
            CppTypeToClassInfo.Add(cppClass, classInfo);
            if (sourceFilePath == null)
            {
                Unsolved.Add(cppClass.FullName, classInfo);
                continue;
            }

            AddClassToFileInfo(sourceFilePath, classInfo);


            var isAbstract = ClassUtil.IsAbstract(cppClass) || !cppClass.HasImplement;
            if (isAbstract)
            {
                cppClass.Constructors.Clear();
            }
            else
            {
                if (cppClass.Constructors.Count == 0)
                {
                    var defaultFunc = new CppFunction(classInfo.ClassType.FullName);
                    defaultFunc.Visibility = CppVisibility.Public;
                    defaultFunc.ReturnType = classInfo.ClassType;
                    defaultFunc.Flags = CppFunctionFlags.Constructor;
                    defaultFunc.IsConstructor = true;
                    defaultFunc.IsDefaultConstructor = true;
                    cppClass.Constructors.Add(defaultFunc);
                }

                if (cppClass.Destructors.Count == 0)
                {
                    var defaultFunc = new CppFunction("Delete");
                    defaultFunc.Visibility = CppVisibility.Public;
                    defaultFunc.ReturnType = CppPrimitiveType.Void;
                    defaultFunc.Flags = CppFunctionFlags.Destructor;
                    defaultFunc.IsDestructor = true;
                    defaultFunc.IsDefaultDestructor = true;
                    cppClass.Destructors.Add(defaultFunc);
                }
            }


            foreach (var constructor in cppClass.Constructors)
            {
                constructor.Name = cppClass.FullName;
                constructor.ReturnType = cppClass;
            }

            foreach (var constructor in cppClass.Destructors)
            {
                constructor.Name = "Delete";
                constructor.Parent = cppClass;
            }

            if (!isAbstract)
            {
                tryAddArrayOptionFunction(cppClass, classInfo);
            }

            tryAddFiledOption(this, cppClass, classInfo);

            foreach (var function in cppClass.Destructors)
            {
                AddFunctionToSortedList(function, classInfo, classInfo.FileInfo, classInfo.FunctionInfos);
            }

            foreach (var function in cppClass.Constructors)
            {
                AddFunctionToSortedList(function, classInfo, classInfo.FileInfo, classInfo.FunctionInfos, true);
            }

            foreach (var function in cppClass.Functions)
            {
                AddFunctionToSortedList(function, classInfo, classInfo.FileInfo, classInfo.FunctionInfos);
            }
        }
    }

    public string GetIncludePath(string fileInfoSourcePath)
    {
        return fileInfoSourcePath.Replace(this.ModuleDir, "").TrimStart("/".ToCharArray())
            .TrimStart("\\".ToCharArray()).Replace("\\", "/");
    }

    private void AddClassToFileInfo(string sourceFilePath, ClassInfo classInfo)
    {
        FileInfos.TryGetValue(sourceFilePath, out var fileInfo);
        if (fileInfo == null)
        {
            fileInfo = new FileInfo(sourceFilePath, this);
            FileInfos.Add(sourceFilePath, fileInfo);
        }

        classInfo.FileInfo = fileInfo;
        fileInfo.ClassInfos.Add(classInfo.ClassType.FullName, classInfo);
        CollectImport(classInfo.ClassType, fileInfo.SortedImport);
        fileInfo.SortedImport.Add(this.GetIncludePath(sourceFilePath));
    }

    private void CollectImport(CppType cppType, SortedSet<string> imports)
    {
        if (!String.IsNullOrEmpty(cppType.SourceFile))
        {
            imports.Add(this.GetIncludePath(cppType.SourceFile));
        }

        if (cppType is CppClass cppClass)
        {
            cppClass.TemplateSpecializedArguments.ForEach(argument =>
            {
                if (argument.ArgAsType != null)
                {
                    var lowestType = ClassUtil.GetLowestType(argument.ArgAsType);
                    if (lowestType.FullName != cppType.FullName)
                    {
                        CollectImport(lowestType, imports);
                    }
                }
            });
        }
    }

    private void tryAddArrayOptionFunction(CppClass cppClass, ClassInfo classInfo)
    {
        var goTypeName = GoTypeUtil.GetGoClassName(cppClass);
        var createArrayFunction = new CppFunction($"New{goTypeName}Array");
        createArrayFunction.IsNewArray = true;
        createArrayFunction.Parameters.Add(new CppParameter(CppPrimitiveType.Long, "n"));
        createArrayFunction.ReturnType = new CppPointerType(cppClass);
        AddFunctionToSortedList(createArrayFunction, classInfo, classInfo.FileInfo, GlobalFunction);

        var deleteArrayFunction = new CppFunction("DeleteArray");
        deleteArrayFunction.IsDeleteArray = true;
        deleteArrayFunction.ReturnType = CppPrimitiveType.Void;
        AddFunctionToSortedList(deleteArrayFunction, classInfo, classInfo.FileInfo, classInfo.FunctionInfos);

        var indexArrayFunction = new CppFunction("IndexArray");
        indexArrayFunction.IsIndexArray = true;
        indexArrayFunction.Parameters.Add(new CppParameter(CppPrimitiveType.Long, "n"));
        indexArrayFunction.ReturnType = new CppPointerType(cppClass);
        AddFunctionToSortedList(indexArrayFunction, classInfo, classInfo.FileInfo, classInfo.FunctionInfos);
    }


    private void AddOneClass(CppClass cppClass, Dictionary<CppClass, string> classToPath)
    {
        if (!ClassUtil.TypeCanExport(this, cppClass)) return;

        classToPath.Add(cppClass, cppClass.SourceFile);
        AddEnums(cppClass.Enums);
        var cppContainerList = cppClass.Classes;
        foreach (var subClass in cppContainerList)
        {
            AddOneClass(subClass, classToPath);
        }
    }

    private void tryAddFiledOption(ModuleInfo moduleInfo, CppClass cppClass, ClassInfo classInfo)
    {
        foreach (var field in cppClass.Fields)
        {
            if (string.IsNullOrEmpty(field.Name))
            {
                continue;
            }

            if (field.Visibility == CppVisibility.Protected || field.Visibility == CppVisibility.Private)
            {
                continue;
            }

            if (field.StorageQualifier == CppStorageQualifier.Static)
            {
                continue;
            }
            classInfo.PropertyInfo.Add(field);

            AddFiledOptions(moduleInfo, field, classInfo);
        }
    }


    private void AddFiledOptions(ModuleInfo moduleInfo, CppField field, ClassInfo classInfo)
    {
   
        var cleanedType = ClassUtil.CleanTypeDefType(field.Type);

        var simpleType = ClassUtil.SimpleType(field.Type);

        if (simpleType is CppReferenceType)
        {

            bool setter = !(cleanedType is CppReferenceType cleanedRefType &&cleanedRefType.ElementType is CppQualifiedType cppQualifiedType&&
                            cppQualifiedType.Qualifier == CppTypeQualifier.Const);
            var replaceFirstRefToPointer = ClassUtil.ReplaceFirstRefToPointer(field.Type);
            var refVisitName = "GetRefPtrOf" + ClassUtil.FirstToUpper(field.Name);
            var refVisitFunction = new CppFunction(refVisitName);
            refVisitFunction.IsRefGetter = true;
            refVisitFunction.ReturnType = replaceFirstRefToPointer;
            refVisitFunction.Comment = field.Comment;
            refVisitFunction.Visibility = CppVisibility.Public;
            refVisitFunction.Flags &= CppFunctionFlags.Method;
            refVisitFunction.PropertyName = field.Name;
            refVisitFunction.PropertyType = field.Type.Clone();
            refVisitFunction.PropertyFieldName = field.ToString();
            if (!ClassUtil.FunctionCanNotExport(this, refVisitFunction, classInfo))
            {
                classInfo.ClassType.Functions.Add(refVisitFunction);
            }

            if (setter)
            {
                var refUpdateName = "UpdateRefPtrOf" + ClassUtil.FirstToUpper(field.Name);
                var refUpdateFunction = new CppFunction(refUpdateName);
                var para = new CppParameter(replaceFirstRefToPointer, "value");
                refUpdateFunction.Parameters.Add(para);
                refUpdateFunction.IsRefUpdater = true;
                refUpdateFunction.ReturnType = CppPrimitiveType.Void;
                refUpdateFunction.Comment = field.Comment;
                refUpdateFunction.Visibility = CppVisibility.Public;
                refUpdateFunction.Flags &= CppFunctionFlags.Method;
                refUpdateFunction.PropertyName = field.Name;
                refUpdateFunction.PropertyType = field.Type.Clone();
                refUpdateFunction.PropertyFieldName = field.ToString();

                if (!ClassUtil.FunctionCanNotExport(this, refUpdateFunction, classInfo))
                {
                    classInfo.ClassType.Functions.Add(refUpdateFunction);
                }
            }
        }
        else
        {
            if (field.IsBitField)
            {
                bool setter = !(field.Type is CppQualifiedType cppQualifiedType &&
                              cppQualifiedType.Qualifier == CppTypeQualifier.Const);
                var refVisitName = "GetBitFieldOf" + ClassUtil.FirstToUpper(field.Name);
                var refVisitFunction = new CppFunction(refVisitName);
                refVisitFunction.IsBitGetter = true;
                refVisitFunction.ReturnType = field.Type;
                refVisitFunction.Comment = field.Comment;
                refVisitFunction.Visibility = CppVisibility.Public;
                refVisitFunction.Flags &= CppFunctionFlags.Method;
                refVisitFunction.PropertyName = field.Name;
                refVisitFunction.PropertyType = field.Type.Clone();
                refVisitFunction.PropertyFieldName = field.ToString();

                if (!ClassUtil.FunctionCanNotExport(this, refVisitFunction, classInfo))
                {
                    classInfo.ClassType.Functions.Add(refVisitFunction);
                }

                if (setter)
                {
                    var refUpdateName = "SetFieldOf" + ClassUtil.FirstToUpper(field.Name);
                    var refUpdateFunction = new CppFunction(refUpdateName);
                    var para = new CppParameter(field.Type, "value");
                    refUpdateFunction.Parameters.Add(para);
                    refUpdateFunction.IsBitSetter = true;
                    refUpdateFunction.ReturnType = CppPrimitiveType.Void;
                    refUpdateFunction.Comment = field.Comment;
                    refUpdateFunction.Visibility = CppVisibility.Public;
                    refUpdateFunction.Flags &= CppFunctionFlags.Method;
                    refUpdateFunction.PropertyName = field.Name;
                    refUpdateFunction.PropertyType = field.Type.Clone();
                    refUpdateFunction.PropertyFieldName = field.ToString();

                    if (!ClassUtil.FunctionCanNotExport(this, refUpdateFunction, classInfo))
                    {
                        classInfo.ClassType.Functions.Add(refUpdateFunction);
                    }
                }
            }
            else
            {
                var propGetterName = "GetPtrTo" + ClassUtil.FirstToUpper(field.Name);
                var cppFunction = new CppFunction(propGetterName);
                cppFunction.IsGetter = true;
                cppFunction.ReturnType = new CppPointerType(field.Type);
                cppFunction.Comment = field.Comment;
                cppFunction.Visibility = CppVisibility.Public;
                cppFunction.Flags &= CppFunctionFlags.Method;
                cppFunction.PropertyName = field.Name;
                cppFunction.PropertyType = field.Type.Clone();
                cppFunction.PropertyFieldName = field.ToString();

                if (ClassUtil.FunctionCanNotExport(this, cppFunction, classInfo))
                {
                    return;
                }

                classInfo.ClassType.Functions.Add(cppFunction);
            }
        }
    }

    public ClassInfo? FindClassInfoOfCppClass(CppClass cppType)
    {
        CppTypeToClassInfo.TryGetValue(cppType, out var ret);
        return ret;
    }

    private void AddFunctionToSortedList(CppFunction cppFunction, ClassInfo? classInfo, FileInfo fileInfo,
        SortedDictionary<string, List<FunctionInfo>> sortedDictionary, bool ctor = false)
    {
        if (ClassUtil.FunctionCanNotExport(this, cppFunction, classInfo))
        {
            return;
        }

        var uCase = Strings.UCase(cppFunction.Name);
        var funcInfo = new FunctionInfo(cppFunction, this, ctor ? null : classInfo, fileInfo);
        sortedDictionary.TryGetValue(uCase, out var globalFunctionList);
        if (globalFunctionList == null)
        {
            globalFunctionList = new List<FunctionInfo>();
            sortedDictionary.Add(uCase, globalFunctionList);
        }

        globalFunctionList.Add(funcInfo);
    }
}